/*******************************************
Module: zt_tcp.c
E-Mail: 24zhutian@gmail.com
Notices: Copyright (c) 2007-2011 ls
*******************************************/
#include "zt_config.h"
#include "zt_tcp.h"

#ifdef _MSC_VER
#pragma comment(lib,"ws2_32.lib")

#define EINPROGRESS WSAEWOULDBLOCK
#endif

ZT_API int zt_tcp_init()
{
#ifdef _MSC_VER
	WSADATA stWsadata;
	WSAStartup(MAKEWORD(2,2),&stWsadata);
#endif
	return(ZT_OK);
}

ZT_API void zt_tcp_clear()
{
#ifdef _MSC_VER
	WSACleanup();
#endif
}

ZT_API zt_socket_t zt_tcp_create()
{
	zt_socket_t s;
	int flag=1;
	
	if((s=socket(AF_INET,SOCK_STREAM,0))==-1){
		return(ZT_ERR);
	}
	if(setsockopt(s,SOL_SOCKET,SO_REUSEADDR,(void *)&flag,sizeof(flag))==1){
		return(ZT_ERR);
	}
	
	return(s);
}

static zt_socket_t zt_tcp_generic_connect(char *addr,int port,int flag)
{
	zt_socket_t s;
	struct sockaddr_in sa;
	struct hostent *he;

	if((s=zt_tcp_create())==ZT_ERR){
		return(ZT_ERR);
	}

	sa.sin_family=AF_INET;
	sa.sin_port=htons((u_short)port);

	he=gethostbyname(addr);
	if(he==NULL){
		zt_close(s);
		return(ZT_ERR);
	}
	memcpy(&sa.sin_addr,he->h_addr,sizeof(struct in_addr));
	if(flag&ZT_TCP_CONNECT_NONBLOCK){
		if(zt_tcp_nonblocking(s)==ZT_ERR){
			zt_close(s);
			return(ZT_ERR);
		}
	}
	if(connect(s,(struct sockaddr*)&sa, sizeof(sa))==-1){
		if(zt_errno==EINPROGRESS&&(flag&ZT_TCP_CONNECT_NONBLOCK)){
			return(s);
		}
		zt_close(s);
		return(ZT_ERR);
	}
	return(s);
}

ZT_API zt_socket_t zt_tcp_connect(char *addr,int port)
{
	return(zt_tcp_generic_connect(addr,port,ZT_TCP_CONNECT_NONE));
}

ZT_API zt_socket_t zt_tcp_connect_nonblock(char *addr,int port)
{
	return(zt_tcp_generic_connect(addr,port,ZT_TCP_CONNECT_NONBLOCK));
}

static int zt_tcp_listen(zt_socket_t s,struct sockaddr *sa,int len)
{
	if(bind(s,sa,len)==-1){
		zt_close(s);
		return(ZT_ERR);
	}
	if(listen(s,511)==-1){ /* 511 is from nginx */
		zt_close(s);
		return(ZT_ERR);
	}
	return(ZT_OK);
}

ZT_API zt_socket_t zt_tcp_server(int port,char *bindaddr)
{
	zt_socket_t s;
	struct sockaddr_in sa;

	if((s=zt_tcp_create())==ZT_ERR){
		return(ZT_ERR);
	}
	memset(&sa,0,sizeof(sa));
	sa.sin_family = AF_INET;
    sa.sin_port = htons((u_short)port);
    sa.sin_addr.s_addr = htonl(INADDR_ANY);

	if(zt_tcp_listen(s,(struct sockaddr *)&sa,sizeof(sa))==ZT_ERR){
		return(ZT_ERR);
	}

	return(s);
}

static zt_inline int zt_tcp_generic_accept(zt_socket_t s,struct sockaddr *sa,int *len)
{
	zt_socket_t fd;

	while(1){
		fd=accept(s,sa,len);
		if(fd==-1){
			if(zt_socket_errno==EINTR){
				continue;
			}
			else{
				return(ZT_ERR);
			}
		}
		break;
	}

	return(fd);
}

ZT_API zt_socket_t zt_tcp_accept(zt_socket_t s,u_char *ip,int *port)
{
	zt_socket_t fd;
	struct sockaddr_in sa;
	int len=sizeof(sa);

	if((fd=zt_tcp_generic_accept(s,(struct sockaddr *)&sa,&len))==ZT_ERR){		
		return(ZT_ERR);
	}

	if(ip){
		strcpy((char *)ip,inet_ntoa(sa.sin_addr));
	}
	if(port){
		*port=ntohs(sa.sin_port);
	}

	return(fd);
}

ZT_API int	zt_tcp_nodelay(zt_socket_t fd)
{
	int flag=1;

	if(setsockopt(fd,IPPROTO_TCP,TCP_NODELAY,(void *)&flag,sizeof(flag))==-1){
		return(ZT_ERR);
	}

	return(ZT_OK);
}

ZT_API int	zt_tcp_keepalive(zt_socket_t fd)
{
	int flag=1;
	
	if(setsockopt(fd,SOL_SOCKET,SO_KEEPALIVE,(void *)&flag,sizeof(flag))==-1){
		return(ZT_ERR);
	}
	
	return(ZT_OK);
}

ZT_API int	zt_tcp_nonblocking(zt_socket_t fd)
{
#ifdef _MSC_VER
	u_long arg = 1;
	if (ioctlsocket(fd, FIONBIO, &arg) == SOCKET_ERROR){		
		return(ZT_ERR);
	}
#else
	int flags;
	
	do{
		flags= fcntl(fd, F_GETFL, 0);
	}while(flags == -1 && (errno == EINTR || errno == EAGAIN));
	
	if(flags == -1){
		return(ZT_ERR);
	}
	else if ((flags & O_NONBLOCK) == 0){
		int rval;
	
		do{
			rval= fcntl(fd, F_SETFL, flags | O_NONBLOCK);
		}while (rval == -1 && (errno == EINTR || errno == EAGAIN));
	
		if(rval==-1)
		{		  
		  return(ZT_ERR);
		}
	}
#endif
	return(ZT_OK);
}
